package nrpc.server;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import javax.security.auth.callback.Callback;

import message.MessageBuilder;
import message.MessageBuilderSupplier;
import message.io.SingleRequest;
import message.reflect.HandlerMapper;
import nrpc.message.DefaultHandlerMapper;
import nrpc.message.InvokeMessageBuilderSupplier;
import nrpc.message.ReturnMessageParser;
import nrpc.message.meta.InvokeMessage;
import nrpc.message.meta.ReturnMessage;
import nrpc.util.AsynMethodTool;

public class ServerStub {
	
	private static final int DEFAULT_BACKLOG = 300;
	
//	private List<ServerThread> serverThreads = new ArrayList<>();
	private ServerThread serverThread;
	private HandlerMapper mapper;
	private int port;
	
	public InetSocketAddress getLocalAddress(){
		return this.serverThread.getAddress();
	}
	
	public ServerStub() {
		this(new InetSocketAddress(0).getPort());
	}
	
	public ServerStub(int port) {
		this(DefaultHandlerMapper.INSTANCE, port);
	}
	
	public ServerStub(HandlerMapper mapper, int port) {
		this.mapper = mapper;
		this.port = port;
	}
	
	public void close() {
		this.serverThread.close();
//		this.serverThreads.forEach(x->x.close());
	}
	
	public void registerInstance(Object obj, Class<?> interfaceClass) throws IOException {
		this.serverThread = new ServerThread(port, DEFAULT_BACKLOG) {	
			private Map<String, Method> methods;
			private Object proxy;
			{
				this.proxy = obj;
				methods = new HashMap<>();
				for(Method x: interfaceClass.getDeclaredMethods()) {
					methods.put(x.getName(), x);
				}
			}
//			@Override
//			protected void deal(SocketChannel c, ByteBuffer buffer) throws IOException {
////				try {
////					System.out.println(c.getRemoteAddress());
////				} catch (IOException e1) {
////					e1.printStackTrace();
////				}
//				.get((x)->{
////					System.out.println(getLocalAddress());
//					new Thread() {
//						private final ByteBuffer outBuffer = ByteBuffer.allocate(1024);
//						@Override
//						public void run() {
//							if(x instanceof InvokeMessage invokeMessage) {
//								Object ret = null;
//								Method m = methods.get(invokeMessage.name);
//								
//								if(AsynMethodTool.isAsyn(m) && AsynMethodTool.isCallback(m)) { // 异步回调函数
//									int len = invokeMessage.args.size();
//									Object[] args = new Object[len+1];
//									for(int i = 0; i<len; i++) args[i] = invokeMessage.args.get(i);
//									args[len] = new Consumer<Object>() {
//										public void accept(Object ret) {
//											var ps = m.getParameters();
//											Type retType = ((ParameterizedType)ps[ps.length-1].getParameterizedType()).getActualTypeArguments()[0];
//											try {
//												SingleRequest.send(c, outBuffer,
//													new ReturnMessageParser(new ReturnMessage(invokeMessage.label, ret), mapper, retType));
//											} catch (IOException e) {
//												e.printStackTrace();
//											}
//										};
//									};
//									try {
//										m.invoke(proxy, args);
//									} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
//										e.printStackTrace();
//									}
//								}
//								else {
//									try {
//										ret = m.invoke(proxy, invokeMessage.args.toArray());
//										SingleRequest.send(c, outBuffer,
//												new ReturnMessageParser(new ReturnMessage(invokeMessage.label, ret), mapper, m.getGenericReturnType()));
//									} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
//										e.printStackTrace();
//									} catch (IOException e) {
//										e.printStackTrace();
//									}
//								}
//								
//							}
//						}
//					}.start();
//				}));
//			}
			@Override
			protected void read(SocketChannel c, ByteBuffer buffer, BiConsumer<? super SocketChannel, ? super Object> callback) throws IOException {
				SingleRequest.recv(c, buffer, new InvokeMessageBuilderSupplier(mapper, methods).get((x)->callback.accept(c, x)));
			}
			@Override
			protected void deal(SocketChannel c, Object obj) {
				ByteBuffer outBuffer = ByteBuffer.allocate(1024);
				if(obj instanceof InvokeMessage invokeMessage) {
					Object ret = null;
					Method m = methods.get(invokeMessage.name);
					
					if(AsynMethodTool.isAsyn(m) && AsynMethodTool.isCallback(m)) { // 异步回调函数
						int len = invokeMessage.args.size();
						Object[] args = new Object[len+1];
						for(int i = 0; i<len; i++) args[i] = invokeMessage.args.get(i);
						args[len] = new Consumer<Object>() {
							public void accept(Object ret) {
								var ps = m.getParameters();
								Type retType = ((ParameterizedType)ps[ps.length-1].getParameterizedType()).getActualTypeArguments()[0];
								try {
									SingleRequest.send(c, outBuffer,
										new ReturnMessageParser(new ReturnMessage(invokeMessage.label, ret), mapper, retType));
								} catch (IOException e) {
									e.printStackTrace();
								}
							};
						};
						try {
							m.invoke(proxy, args);
						} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
							e.printStackTrace();
						} 
					}
					else {
						try {
							ret = m.invoke(proxy, invokeMessage.args.toArray());
							SingleRequest.send(c, outBuffer,
									new ReturnMessageParser(new ReturnMessage(invokeMessage.label, ret), mapper, m.getGenericReturnType()));
						} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
							e.printStackTrace();
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
					
				}
			}
		};
//		this.serverThreads.add(serverThread);
		serverThread.start();
	}
	
}
